Source code for hysop.backend.hardware.machine

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import platform, math, itertools as it
from hysop.constants import System
from hysop.tools.units import bytes2str
from hysop.tools.contexts import printoptions
from hysop.tools.cache import machine_id
from hysop.backend.hardware.hwinfo import TopologyObject
from hysop.backend.hardware.cpu import CpuPackage
from hysop.backend.hardware.pci import PciBridge


[docs] class NumaNode(TopologyObject): """ A set of processors around memory which the processors can directly access. """ def __init__(self, parent, node, package=None, bridge=None): if node is not None: self._packages = [] self._bridges = [] super().__init__(parent, node) else: assert package is not None self._attributes = {} self._packages = [package] self._bridges = [bridge] self.parent = parent
[docs] def packages(self): return sorted(self._packages, key=lambda x: x.os_index())
[docs] def bridges(self): return sorted(self._bridges, key=lambda x: x.os_index())
[docs] def local_memory(self): return self.attribute("local_memory")
[docs] def node_set(self): return self.attribute("nodeset")
[docs] def node_mask(self): nodeset = self.node_set() mask_length = int(math.ceil(math.log(self.parent.full_node_set(), 2))) _nodeset = "|{0:0{length}b}|".format(nodeset, length=mask_length) _nodeset = _nodeset.replace("0", ".").replace("1", "x") _nodeset += " 0x{0:0{length}x}".format(nodeset, length=mask_length // 4) return _nodeset
[docs] def cpu_packages_count(self): return len(self._packages)
[docs] def physical_cores_count(self): return sum(x.physical_cores_count() for x in self._packages)
[docs] def processing_units_count(self): return sum(x.processing_units_count() for x in self._packages)
[docs] def pci_bridge_count(self): return len(self._bridges)
[docs] def pci_devices_count(self): return sum(x.pci_device_count() for x in self._bridges)
[docs] def cpu_packages(self): return self.packages()
[docs] def pci_devices(self): return it.chain.from_iterable([x.leaf_pci_devices() for x in self.bridges()])
def __str__(self): return self.to_string()
[docs] def to_string(self, expand_pci_tree=True): header = f"-- NUMA Node {self.os_index()} --" content = """ nodeset: {} cpuset: {} node memory: {} """.format( self.node_mask(), self.cpu_mask(), bytes2str(self.local_memory(), decimal=True), ) for package in self.packages(): content += str(package) if self.pci_bridge_count() > 0: content += "\n::PCI bus topology::\n" for bridge in self.bridges(): content += self.indent( bridge.to_string(expand_pci_tree=expand_pci_tree) ) return header + self.indent(content)
def _parsed_type(self): return "NUMANode" def _parse_object(self, it): _type = it.attrib["type"] if _type == "Package": obj = CpuPackage(self, it) self._packages.append(obj) elif _type == "Bridge": obj = PciBridge(self, it) self._bridges.append(obj) else: raise ValueError(f"Unknown object type {_type}.")
[docs] @classmethod def from_package(cls, parent, package, bridge, attributes): """ Build a virtual NUMA node when there is only one socket. """ node = NumaNode(node=None, parent=parent, package=package, bridge=bridge) node.update_attributes(attributes) return node
[docs] class Machine(TopologyObject): """ Class describing a physical machine (a set of processors and memory). """ def __init__(self, parent, machine): if platform.system() == "Windows": system = System.WINDOWS elif platform.system() == "Darwin": system = System.DARWIN elif platform.system() == "Linux": system = System.LINUX else: msg = f"Unknown platform system {platform.system()}." raise ValueError(msg) self._system = system self._bridge, self._package = None, None self._numa_nodes = [] super().__init__(parent, machine) def _post_init(self): if self._package: self._attributes["nodeset"] = 1 attr = { "local_memory": self.pop_attr("local_memory"), "os_index": self.pop_attr("os_index"), "cpuset": self.cpu_set(), "nodeset": 1, } self._numa_nodes = [ NumaNode.from_package( parent=self, attributes=attr, package=self._package, bridge=self._bridge, ) ] elif self._numa_nodes: pass else: raise RuntimeError("Something went wrong during parsing.") def _parsed_type(self): return "Machine" def _handle_child(self, child): if child.tag == "page_type": self._parse_page_type(child) else: super()._handle_child(child) def _parse_page_type(self, it): pass def _parse_object(self, it): _type = it.attrib["type"] if _type == "Package": assert self._package is None self._package = CpuPackage(self, it) elif _type == "Bridge": assert self._bridge is None self._bridge = PciBridge(self, it) elif _type == "NUMANode": self._numa_nodes.append(NumaNode(self, it)) else: raise ValueError( f"Unknown object type {_type} obtained during Machine parsing." ) ## Machine information
[docs] def system(self): return self._system
[docs] def numa_nodes(self): return sorted(self._numa_nodes, key=lambda x: x.os_index())
[docs] def numa_nodes_count(self): return len(self._numa_nodes)
[docs] def distances(self): if "distances" in self._attributes: return self.attribute("distances") else: return None
[docs] def node_set(self): return self.attribute("nodeset")
[docs] def full_node_set(self): return self.node_set()
[docs] def total_memory(self): return sum(x.local_memory() for x in self._numa_nodes)
[docs] def cpu_packages_count(self): return sum(x.cpu_packages_count() for x in self._numa_nodes)
[docs] def physical_cores_count(self): return sum(x.physical_cores_count() for x in self._numa_nodes)
[docs] def processing_units_count(self): return sum(x.processing_units_count() for x in self._numa_nodes)
[docs] def pci_devices_count(self): return sum(x.pci_device_count() for x in self._numa_nodes)
[docs] def cpu_packages(self): return [ cpu for cpu in it.chain.from_iterable( [x.cpu_packages() for x in self.numa_nodes()] ) ]
[docs] def pci_devices(self, vendor_id=None, device_id=None): devices = it.chain.from_iterable([x.pci_devices() for x in self.numa_nodes()]) if vendor_id is not None: devices = filter(lambda x: x.pci_system_vendor_id() == vendor_id, devices) if device_id is not None: devices = filter(lambda x: x.pci_system_device_id() == device_id, devices) return tuple(devices)
[docs] def architecture(self): return self.attribute("architecture")
[docs] def backend(self): return "{} {}".format(self.attribute("backend"), self.attribute("architecture"))
[docs] def os(self): return "{} {} ({})".format( self.attribute("os_name"), self.attribute("os_release"), self.attribute("os_version"), )
[docs] def bios(self): return "{} v.{} ({})".format( self.attribute("bios_vendor"), self.attribute("bios_version"), self.attribute("bios_date"), )
[docs] def board(self): return "{} {} {}".format( self.attribute("board_vendor"), self.attribute("board_name"), self.attribute("board_version"), )
[docs] def hwinfo_version(self): return "{} (hwinfo) v.{}".format( self.attribute("process_name"), self.attribute("hwloc_version") )
def __str__(self): return self.to_string(expand_pci_tree=True)
[docs] def to_string(self, expand_pci_tree=True): header = "== Physical Hardware Report ==" content = """ bios: {} board: {} board_id: {} backend: {} OS: {} nodeset: 0x{:x} cpuset: 0x{:x} NUMA nodes: {} CPU packages: {} physical cores: {} processing units: {} physical memory: {} """.format( self.bios(), self.board(), machine_id, self.backend(), self.os(), self.node_set(), self.cpu_set(), self.numa_nodes_count(), self.cpu_packages_count(), self.physical_cores_count(), self.processing_units_count(), bytes2str(self.total_memory(), decimal=True), ) for node in self.numa_nodes(): content += "\n" + node.to_string(expand_pci_tree=expand_pci_tree) + "\n" distances = self.distances() if distances is not None: content += "\nRelative latency matrix between NUMA nodes:" with printoptions(precision=2): content += f"\n{distances}" content += "\n" content += f"\nHardware info gathered with {self.hwinfo_version()}" footer = "\n====================" return header + self.indent(content) + footer